<template>
	<!-- 聊天室 页面 -->
	<view class="content">
		<view class="left" :class="[{'collapsed': isCollapsed}]">
			<view class="top">
				<view class="item hover" @click="addChatSession">
					<uni-icons color="#d0d0d0" type="plusempty" style="margin-right: 10px;" />
					New Chat
				</view>
				<view class="item hover" @click="choiceMask">
					<uni-icons color="#d0d0d0" custom-prefix="iconfont" type="icon-mask" style="margin-right: 10px;" />
					角色面具
				</view>
			</view>
			<view class="middle">
				<view class="chat hover" :class="message.chatID === item.chatID ? 'current' : ''"
					v-for="(item, index) in ChatSession" :key="item.chatID">
					<view class="chatTitle" @click="getChatHistory(item.chatID)">
						<uni-icons color="#d0d0d0" type="chatbubble" style="margin-right: 10px;" />
						<view class="chatTitleText">{{ item.title }}</view>
					</view>
					<uni-icons class="delete" color="#999" type="trash-filled" size="20"
						@click="deleteChat(item.chatID)" />
				</view>
			</view>
			<view class="bottom hover">
				<view class="user" @click="gotoPersonalPage">
					<uni-icons custom-prefix="iconfont" color="#d0d0d0" type="icon-person" size="20"
						style="margin-right: 10px;" />
					<view class="nickname">{{ user.info.username }}</view>
				</view>
				<uni-icons custom-prefix="iconfont" color="#d0d0d0" type="icon-logout" size="20" @click="logout" />
			</view>
		</view>
		<view class="mask" @click="flodClick" v-if="!isCollapsed && (deviceType === 'phone' || windowWidth < 768)" />
		<view class="right">
			<view class="header">
				<uni-icons custom-prefix="iconfont" size="30" :type="isCollapsed ? 'icon-expand' : 'icon-fold'"
					@click="flodClick" class="expand" />
				<uni-icons class="header-list-icon" type="list" size="20" @click="flodClick" />
				<image src="/static/logo.png" />
				<view>云系统</view>
			</view>
			<view class="chatView" @scroll='handleScroll'>
				<chat v-for="item in message.list" :key="item.creat_time" :role="item.role" :state='item.state'
					:content="item.content" />
			</view>
			<view class="bottom">
				<view class="inputarea">
					<textarea class="textarea" maxlength="-1" auto-height v-model="inputText"
						placeholder="按\键激活输入框  Shift+Enter换行  Enter发送" />
					<uni-icons :color="inputText.length ? '#000000' : '#d0d0d0'" type="right" size="30"
						@click="send_msg"></uni-icons>
				</view>
			</view>
		</view>
		<uni-popup ref="maskpopup">
			<view class="mask_container">
				<view class="top">
					<view class="title">预设角色面具 --{{mask.list.length}}个内置预设角色</view>
					<uni-icons type="closeempty" size="26" class="quit" @click="mask_quit" />
				</view>
				<uni-easyinput trim="all" class="input" v-model="mask.input" placeholder="请输入角色面具"
					placeholderStyle='text-align: center;' @input='filter_mask' />
				<view class="scroll-view">
					<view class="mask-item" v-for="(item,index) in mask.list" :key="item.mask_id"
						:class="{'border-line': index !== mask.list.length - 1}">
						<view class="mask-item-left">
							<view class="mask-title" v-html="highlightText(item.title)" />
							<view class="mask-desc">包含 {{item.desc.split("|").length}} 条预设对话 / {{item.model}}</view>
						</view>
						<view class="mask-item-right" @click="addChatSessionWithMask(item.mask_id)"><uni-icons
								type="plus" style="margin-right: 5px;" />对话</view>
					</view>
				</view>
			</view>
		</uni-popup>
		<uni-popup ref="message" type="message">
			<uni-popup-message :type="popupMessage.msgType" :message="popupMessage.messageText"
				:duration="2000"></uni-popup-message>
		</uni-popup>
		<uni-popup ref="alertDialog" type="dialog">
			<uni-popup-dialog type="info" cancelText="点错了哇" confirmText="走了走了" title="提示" content="这么快就想退出登录了呢？"
				@confirm="logoutConfirm"></uni-popup-dialog>
		</uni-popup>
	</view>
</template>

<script>
	import api from '@/static/js/api';
	import {
		BASE_URL
	} from '@/static/js/config.js';
	export default {
		data() {
			return {
				message: {
					chatID: undefined,
					list: [],
				},
				inputText: '',
				textarea: undefined,
				sending: false,
				popupMessage: {
					msgType: '',
					messageText: ''
				},
				ChatSession: [],
				user: {
					username: '',
					token: ''
				},
				scrollTop: 0,
				isCollapsed: false,
				deviceType: 'window',
				mask: {
					input: '',
					list: [],
					temp: [],
				},
				windowWidth: 0,
				bottom: true
			};
		},
		onLoad() {
			try {
				api.getCsrfToken()
				this.user = uni.getStorageSync('user')
				this.getChatSession()
				this.windowWidth = uni.getSystemInfoSync().windowWidth
				uni.onWindowResize(res => this.windowWidth = res.size.windowWidth);
			} catch {
				this.redirectToLoginPage()
			}
		},
		onReady() {
			const device = uni.getSystemInfoSync()
			this.deviceType = device.deviceType
			if (device.deviceType === 'phone') this.flodClick()
		},
		mounted() {
			this.textarea = document.getElementsByTagName('textarea')[0]
			this.textarea.addEventListener('keydown', this.handleKeyDown)
			document.addEventListener('keydown', (event) => {
				if (event.keyCode === 220) this.textarea.focus()
			})
		},
		beforeDestroy() {
			this.textarea.removeEventListener('keydown', this.handleKeyDown)
		},
		methods: {
			logout() {
				this.$refs.alertDialog.open()
			},
			logoutConfirm() {
				uni.removeStorageSync('user');
				this.messagePopUP('success', '退出登录')
				this.redirectToLoginPage()
			},
			redirectToLoginPage() {
				setTimeout(() => {
					uni.redirectTo({
						url: '/pages/index/index'
					})
				}, 2000)
			},
			messagePopUP(type, message) {
				this.popupMessage.msgType = type
				this.popupMessage.messageText = message
				this.$refs.message.open()
			},
			flodClick() {
				this.isCollapsed = !this.isCollapsed
				const left = document.querySelector('.left');
				if (this.isCollapsed) setTimeout(() => {
					left.style.padding = '0px';
				}, 200)
				else left.style.padding = '10px'
			},
			choiceMask() {
				this.$refs.maskpopup.open('center')
				api.getmasklist().then(response => {
					if (response.code === 200) this.mask.list = this.mask.temp = response.msg
					else this.messagePopUP("warn", response.msg)
				}).catch(error => {
					this.messagePopUP("error", "网络请求失败")
				})
			},
			highlightText(text) {
				if (this.mask.input) return text.replace(new RegExp(`(${this.mask.input})`, 'gi'),
					`<span style="color: red;">${this.mask.input}</span>`)
				else return text
			},
			filter_mask(e) {
				if (e.length > 0) this.mask.list = this.mask.temp.filter(item => item.title.includes(e))
				else this.mask.list = this.mask.temp
			},
			mask_quit() {
				this.$refs.maskpopup.close()
			},
			formateDateTime(date) {
				const year = date.getFullYear(); // 获取年份
				const month = String(date.getMonth() + 1).padStart(2, '0')
				const day = String(date.getDate()).padStart(2, '0')
				const hours = String(date.getHours()).padStart(2, '0')
				const minutes = String(date.getMinutes()).padStart(2, '0')
				const seconds = String(date.getSeconds()).padStart(2, '0')
				return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
			},
			insertMessage(role, content) {
				this.message.list.push({
					role: role,
					content: content,
					create_time: this.formateDateTime(new Date()),
					state: 0
				})
				return this.message.list.length - 1
			},
			handleKeyDown(e) {
				const huanhang = () => {
					const startPos = this.textarea.selectionStart
					const endPos = this.textarea.selectionEnd
					this.inputText = this.inputText.substring(0, startPos) + "\n" + this.inputText.substring(endPos)
				}

				if ((e.shiftKey && e.keyCode === 13) || (e.keyCode === 13 && this.deviceType !== 'pc')) huanhang()
				else if (e.keyCode === 13 && this.deviceType === 'pc') e.preventDefault(), this.send_msg()
			},
			send_msg() {
				const that = this
				this.inputText = this.inputText.trim();
				if (!this.inputText) return this.messagePopUP('warn', '写点东西嘛~~不然让人家怎么回你')
				if (this.sending) return this.messagePopUP('warn', '让人家说完再发嘛~')

				function b() {
					return new Promise((resolve, reject) => {
						api.getChatSession().then(response => {
							resolve(response)
						}).catch(erroe => {
							reject(error);
						})
					})
				}

				function a() {
					return new Promise((resolve, reject) => {
						api.ChatSession('post', null).then(res => {
							const response = that.parserResponse(res)
							if (res.code === 200) {
								b().then((response) => {
									if (response.code === 200) that.ChatSession = response.msg
									else that.messagePopUP('warn', response.msg)
									resolve(response);
								}).catch((error) => {
									that.messagePopUP('error', error)
									reject(error)
								})
								that.message.chatID = res.msg
							} else that.messagePopUP('warn', response)
						}).catch(error => {
							that.messagePopUP('error', error)
							reject(error);
						})
					})
				}
				async function c() {
					try {
						if (that.ChatSession.length === 0) await a();
						if (that.message.chatID === undefined) {
							that.ChatSession.forEach(item => {
								if (item.title === '新的会话') that.message.chatID = item.chatID
							})
							if (that.message.chatID === undefined) await a();
						}
						let CurrentChatIndex = that.ChatSession.findIndex(item => item.chatID === that.message.chatID)
						if (that.ChatSession[CurrentChatIndex].title === '新的会话') that.ChatSession[CurrentChatIndex]
							.title = that.inputText
						that.insertMessage('user', that.inputText)
						that.inputText = ''
						that.scollTobottom()
						const formateData = () => {
							let obj = that.message.list.at(-1)
							return JSON.stringify({
								role: obj.role,
								content: obj.content
							})
						}
						const sendFetch = async () => {
							try {
								that.sending = true
								const data = await fetch(BASE_URL + "chat-process/", {
									headers: {
										'X-CSRFTOKEN': uni.getStorageSync('csrfmiddlewaretoken'),
										"JSONWebToken": that.user.token,
										"userID": that.user.info.userID
									},
									method: 'POST',
									responseType: 'stream',
									body: new URLSearchParams({
										message: formateData(),
										model: uni.getStorageSync('model') || 'gpt-3.5-turbo',
										chatID: that.message.chatID
									})
								})
								const index = that.insertMessage('assistant', '')
								const reader = data.body.getReader()
								that.message.list[index - 1].state = 1
								let buffer = new Uint8Array();
								while (true) {
									const {
										done,
										value
									} = await reader.read()
									if (done) {
										const text = new TextDecoder().decode(buffer)
										if (text.length === 0) {
											that.message.list[index].content = 'ChatGpt Key被限制，无返回内容'
											if (that.bottom) that.scollTobottom()
										}
										that.message.list[index].state = 1
										that.sending = false
										break
									}
									buffer = new Uint8Array([...buffer, ...value]);
									try {
										const content = JSON.parse(new TextDecoder().decode(buffer))
										if (content) {
											if (content.msg.indexOf('Token') !== -1) {
												that.messagePopUP('warn', content.msg)
												return that.redirectToLoginPage()
											}
										}
									} catch (e) {
										that.message.list[index].content = new TextDecoder().decode(buffer);
										if (that.bottom) that.scollTobottom()
									}
								}
							} catch (err) {
								that.message.list[--that.message.list.length].state = -1
								that.messagePopUP('error', '网络故障，请稍后再尝试')
								setTimeout(() => {
									that.messagePopUP('error', String(err))
								}, 2000)
								that.sending = false
							}
						}
						sendFetch()
					} catch (error) {
						that.messagePopUP('warn', error)
					}
				}
				c()
			},
			scollTobottom() {
				this.$nextTick(function() {
					let dom = document.querySelector('.chatView');
					dom.scrollTop = dom.scrollHeight - dom.offsetHeight;
				});
			},
			handleScroll() {
				let dom = document.querySelector('.chatView');
				this.bottom = dom.scrollTop === dom.scrollHeight - dom.offsetHeight ? true : false
			},
			getChatSession() {
				api.getChatSession().then(res => {
					const response = this.parserResponse(res)
					if (res.code === 200) return this.ChatSession = res.msg
					else return this.messagePopUP('warn', res.msg)
				})
			},
			addChatSession() {
				api.ChatSession('post', null).then(res => {
					const response = this.parserResponse(res)
					if (res.code === 200) return this.getChatSession()
					else return this.messagePopUP('warn', response)
				})
			},
			getChatHistory(chatid) {
				if (this.message.chatID == chatid) return
				api.getChatHistory(chatid).then(res => {
					const response = this.parserResponse(res)
					if (res.code !== 200) return this.messagePopUP('warn', response)
					this.message.list = res.msg
					this.message.chatID = chatid
					this.scollTobottom()
				})
			},
			addChatSessionWithMask(maskId) {
				api.addSessionWithMask(maskId).then(response => {
					if (response.code === 200) {
						const chatID = response.msg
						this.message.chatID = chatID
						this.getChatSession()
						this.message.list = []
						console.log(this.windowWidth)
						if (this.windowWidth <= 768) this.flodClick()
						this.$refs.maskpopup.close()
					} else return this.messagePopUP('warn', response.msg)
				})
			},
			deleteChat(chatID) {
				api.ChatSession('delete', chatID).then(res => {
					const response = this.parserResponse(res)
					if (res.code === 200) this.getChatSession()
					if (this.message.chatID === chatID) {
						this.message.list = []
						this.message.chatID = undefined
					}
				})
			},
			gotoPersonalPage() {
				uni.navigateTo({
					url: '/pages/personal/personal'
				})
			},
			parserResponse(response) {
				const code = response.code
				if (code === 0 || code === -1 || code === -2 || code === -3) this.redirectToLoginPage()
				return response.msg
			}
		}
	}
</script>

<style lang="scss" scoped>
	$background-color: #202123;
	$font-color: #d0d0d0;

	.expand {
		border-radius: 5px;
		padding: 5px;
		position: absolute;
		left: 5px;

		&:hover {
			background-color: rgba(0, 0, 0, .1);
		}

		@media (max-width: 767px) {
			visibility: hidden;
		}
	}

	.current,
	.hover:hover {
		background-color: #2B2C2F;
	}

	.hover:hover .delete {
		display: block !important;
	}

	.content {
		display: flex;
	}

	.collapsed {
		width: 0 !important;
	}

	.left {
		background-color: $background-color;
		color: $font-color;
		height: 100vh;
		width: 300px;
		padding: 10px;
		display: flex;
		flex-direction: column;
		justify-content: space-between;
		z-index: 5;
		transition: width 0.3s ease;

		* {
			overflow: hidden;
		}

		.top {
			display: flex;
			align-items: center;
			justify-content: space-between;

			.item {
				display: flex;
				align-items: center;
				padding: 10px;
				border: 1px solid #4D4D4F;
				width: 45%;
				border-radius: 5px;
				font-size: small;
				overflow: hidden;
				white-space: nowrap;
			}

			.fold {
				padding: 10px;
				border-radius: 5px;
				border: 1px solid #4D4D4F;
			}
		}

		.middle {
			flex: 1;
			margin: 20px 0;
			padding: 10px 0;
			border-top: 1px solid #4D4D4F;
			border-bottom: 1px solid #4D4D4F;
			overflow-y: scroll;

			&:empty::before {
				content: "没有会话记录";
				width: 100%;
				min-width: 280px;
				height: 100%;
				display: flex;
				align-items: center;
				justify-content: center;
				color: #555;
			}

			.chat {
				display: flex;
				align-items: center;
				padding: 10px;
				margin-bottom: 10px;
				overflow: hidden;
				text-overflow: ellipsis;
				white-space: nowrap;
				position: relative;
				min-width: 280px;

				.delete {
					display: none;
				}

				.chatTitle {
					display: flex;
					align-items: center;
					overflow: hidden;
					text-overflow: ellipsis;
					width: 240px;
				}

				.chatTitleText {
					overflow: hidden;
					text-overflow: ellipsis;
					max-width: 215px;
				}
			}
		}

		.bottom {
			width: 100%;
			display: flex;
			align-items: center;
			justify-content: space-between;
			padding: 10px;
			z-index: 10;

			.user {
				display: flex;
				align-items: center;
				width: 100%;

				view {
					margin-left: 10px;
					display: flex;
					line-height: 30px;
					align-items: center;
				}
			}
		}

		@media (max-width: 767px) {
			position: absolute;
		}
	}

	.right {
		flex: 1;
		display: flex;
		flex-direction: column;
		align-items: center;
		height: 100vh;
		min-width: 0;

		.header {
			height: 50px;
			width: 100%;
			display: flex;
			align-items: center;
			justify-content: center;
			position: unset;
			background-color: rgba(255, 255, 255, .6);

			@media (max-width: 767px) {
				width: 100% !important;
			}

			.logout {
				position: absolute;
				right: 10px;
			}

			.header-list-icon {
				position: absolute;
				left: 10px;

				@media (min-width: 768px) {
					visibility: hidden;
				}
			}

			image {
				height: 70px;
				width: 70px;
				border-radius: 50%;
				margin-right: 10px;
			}

			view {
				font-size: large;
				letter-spacing: 5px;
			}
		}

		.chatView {
			flex: 1;
			overflow-y: scroll;
			padding: 10px;
			max-width: 768px;
			width: 100%;

			@media (max-width: 767px) {
				width: 100vw;
			}
		}

		.bottom {
			width: 100%;
			padding: 10px 0;
			background-color: rgba(255, 255, 255, .6);
			display: flex;
			justify-content: center;
			align-items: center;

			.inputarea {
				border: 1px solid #ececec;
				border-radius: 10px;
				padding: 10px 20px;
				display: flex;
				align-items: center;
				box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1), 0 2px 30px rgba(0, 0, 0, 0.1);
				background-color: white;

				.textarea {
					width: 100%;
					overflow: auto;
					max-height: 4em;
				}

				@media (min-width: 767px) {
					max-width: 700px;
					width: 80%;
				}

				@media (max-width: 767px) {
					margin: 0 10px;
					width: 100%;
				}
			}

			@media (max-width: 767px) {
				width: 100% !important;
			}
		}
	}

	.mask {
		position: absolute;
		width: 100vw;
		height: 100vh;
		z-index: 2;
		background-color: rgba(0, 0, 0, .3);
	}

	.mask_container {
		background-color: #fff;
		width: 60vw;
		height: 80vh;
		border-radius: 20px;
		padding: 20px 10px;
		display: flex;
		flex-direction: column;

		@media (max-width: 767px) {
			width: 100vw;
			height: 100vh;
			border-radius: 0;
		}

		.uni-easyinput {
			flex: unset;
		}

		.top {
			display: flex;
			justify-content: space-between;
			align-items: center;
			margin-bottom: 10px;

			.title {
				font-size: 1.3rem;
				font-weight: bold;
			}

			.quit {
				padding: 5px;
				border: 1px solid #dedede;
				border-radius: 5px;

				&:hover {
					background-color: rgba(200, 200, 200, .3);
				}
			}
		}

		.input {
			text-align: center;
		}

		.scroll-view {
			margin: 10px 0;
			overflow-y: scroll;
			border: 1px solid #dedede;
			padding: 10px;
			border-radius: 10px;
			flex: 1;

			&:empty::before {
				content: '没有查找到符合的数据噢~';
				display: flex;
				justify-content: center;
				align-items: center;
				height: 100%;
				color: #cecece;
			}
		}

		.border-line {
			border-bottom: 1px solid #dedede;
		}

		.mask-item {
			display: flex;
			align-items: center;
			justify-content: space-between;
			padding: 10px;

			opacity: 0;
			transform: translateY(100%);
			animation: fade-in-from-bottom-animation .5s ease-in forwards;

			@keyframes fade-in-from-bottom-animation {
				0% {
					opacity: 0;
					transform: translateY(100%);
				}

				100% {
					opacity: 1;
					transform: translateY(0);
				}
			}

			.mask-title {
				font-size: large;
				font-weight: bold;
				margin-bottom: 5px;
			}

			.mask-desc {
				color: #999;
				font-size: small;
			}

			.mask-item-right {
				padding: 5px;

				&:hover {
					background-color: rgba(200, 200, 200, .3);
					border-radius: 10px;
				}
			}
		}
	}
</style>